﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using GeneralDFS.BusinessLogical.Config;
using GeneralDFS.BusinessLogical.IO;
using GeneralDFS.Common;
using GeneralDFS.DataEntity.IO;
using GeneralDFS.DataEntity.Config;

namespace GeneralDFS.BusinessLogical.Network
{
    public class AsyncClientTcpListener
    {
        public delegate void OnDownLoadProgressBegin(object sender, DownLoadProgressChangedEvent eventArgs);
        public delegate void OnDownLoadProgressChanged(object sender, DownLoadProgressChangedEvent eventArgs);
        public delegate void OnDownLoadProgressCompleted(object sender, DownLoadProgressChangedEvent eventArgs);

        public event OnDownLoadProgressBegin DownLoadProgressBegin;
        public event OnDownLoadProgressChanged DownLoadProgressChanged;
        public event OnDownLoadProgressCompleted DownLoadProgressCompleted;

        public DownLoadProgressChangedEvent eventArgs;
        public AsyncClientTcpListener() { }
        private readonly String _filePath;
        private readonly TcpListener _tcpListener;
        private readonly long _length;
        private DFSConfig config;

        public AsyncClientTcpListener(DFSConfig config, IPAddress address, int port, string filePath, long length)
        {
            this.config = config;
            _tcpListener = new TcpListener(address, port);
            _filePath = filePath;
            _length = length;
        }

        public void Read()
        {
            _tcpListener.Start();
            var accpetDone = new ManualResetEvent(false);
            var acceptState = new CustomTcpState() { TcpListener = _tcpListener, AllDone = accpetDone, FilePath = _filePath };

            var count = _length % config.DetailConfig.BlockSize == 0
                        ? _length / config.DetailConfig.BlockSize
                        : _length / config.DetailConfig.BlockSize + 1;

            for (int i = 0; i < count; i++)
            {
                _tcpListener.BeginAcceptTcpClient(AcceptSocketCallBack, acceptState);
                accpetDone.WaitOne();
                accpetDone.Reset();
            }
        }

        public void AcceptSocketCallBack(IAsyncResult result)
        {
            var acceptState = result.AsyncState as CustomTcpState;
            acceptState.AllDone.Set();
            var tcpClient = acceptState.TcpListener.EndAcceptTcpClient(result);
            var networkStream = tcpClient.GetStream();
            var state = new CustomTcpState() { Guid = Guid.NewGuid(), NetworkStream = networkStream };
            ReadMetaData(ref state); //读取元数据
            state.Buffer = new byte[config.DetailConfig.BuffSize];
            state.BufferSize = state.Buffer.Length;
            state.PreBuffer = new byte[config.DetailConfig.BuffSize];
            state.PreBufferSize = state.PreBuffer.Length;
            state.FileStream = new FileStream(acceptState.FilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write);
            state.FileStream.Seek(state.FileBlockMetaData.Offset, SeekOrigin.Begin);
            state.Position = 0;
            state.AllDone = new ManualResetEvent(false);
            state.AllDone.Reset();
            state.PreEvent = new ManualResetEvent(false);
            state.PreEvent.Reset();
            LogUtil.LogInfo("=======Begin Read BlockData From NetworkStream！=====");
            networkStream.BeginRead(state.Buffer, 0, state.BufferSize, ReadDataCallBack, state);
            if (eventArgs == null) eventArgs = new DownLoadProgressChangedEvent() { HasRead = 0, Length = _length, Offset = 0 };
            if (DownLoadProgressBegin != null && eventArgs.HasRead == 0)
            {
                eventArgs.HasRead = 0;
                DownLoadProgressBegin(state, eventArgs);
            }
            if (DownLoadProgressChanged != null)
            {
                eventArgs.HasRead += state.BufferSize;
                DownLoadProgressBegin(state, eventArgs);
            }
            state.AllDone.WaitOne();
            state.AllDone.Reset();
        }

        /// <summary>
        /// 读取Block数据
        /// </summary>
        /// <param name="result"></param>
        public void ReadDataCallBack(IAsyncResult result)
        {
            var state = result.AsyncState as CustomTcpState;
            state.AllDone.Set();
            var networkStream = state.NetworkStream;
            if (networkStream == null || null == state.FileStream) return;
            try
            {
                var recv = state.NetworkStream.EndRead(result);
                if (state.Position != 0)
                {
                    state.PreEvent.WaitOne();
                    state.PreEvent.Reset();
                    state.PreBuffer.CopyTo(state.Buffer,0);
                }
                state.FileStream.BeginWrite(state.Buffer, 0, recv, WriteCallBack, state);
                state.Position += recv;
                //state.FileStream.Flush(true);
                if (state.Position == state.FileBlockMetaData.Length)
                {
                    state.PreEvent.WaitOne();
                    state.PreEvent.Reset();
                    state.FileStream.Flush(true);
                    state.FileStream.Close();
                    LogUtil.LogInfo("FileStream Closed!");
                    networkStream.Flush();
                    networkStream.Close();
                    LogUtil.LogInfo("NetworkStream Closed!");
                    LogUtil.LogInfo("=======End Read BlockData From NetworkStream！=====");
                    LogUtil.LogInfo(string.Format("=======Update FileBlockInfo at Path:{0}=====", state.FileBlockMetaData.FullPath));
                    if (eventArgs.Length == _length && null != DownLoadProgressCompleted)
                    {
                        DownLoadProgressCompleted(state, eventArgs);
                    }
                }
                else
                {
                    networkStream.BeginRead(state.PreBuffer, 0, state.PreBufferSize, ReadDataCallBack, state);
                    if (DownLoadProgressChanged != null)
                    {
                        eventArgs.HasRead += state.PreBufferSize;
                        DownLoadProgressBegin(state, eventArgs);
                    }
                    state.AllDone.WaitOne();
                    state.AllDone.Reset();
                }
            }
            catch (Exception ex)
            {
                LogUtil.LogError(ex);
            }
        }

        private void WriteCallBack(IAsyncResult result)
        {
            var writeState = result.AsyncState as CustomTcpState;
            writeState.FileStream.EndWrite(result);
            writeState.PreEvent.Set();
        }

        /// <summary>
        /// 读取元数据
        /// </summary>
        /// <param name="state"></param>
        public void ReadMetaData(ref CustomTcpState state)
        {
            LogUtil.LogInfo("=====Begin to read MetaData=====");
            var stream = state.NetworkStream;
            var metaDataLengthBytes = new byte[sizeof(int)];
            int length = stream.Read(metaDataLengthBytes, 0, metaDataLengthBytes.Length);
            int metaDataLength = BitConverter.ToInt32(metaDataLengthBytes, 0);
            LogUtil.LogInfo(String.Format("=====MetaData Length:{0}", length));
            var metaDataStream = new MemoryStream();
            metaDataStream.Seek(0, SeekOrigin.Begin);
            int count = 0;
            var buffer = new byte[config.DetailConfig.BuffSize];
            try
            {
                while (count < metaDataLength)
                {
                    if (metaDataLength - count < config.DetailConfig.BuffSize)
                    {
                        length = stream.Read(buffer, 0, metaDataLength - count);
                        metaDataStream.Write(buffer, 0, length);
                        break;
                    }
                    else
                    {
                        length = stream.Read(buffer, 0, buffer.Length);
                        metaDataStream.Write(buffer, 0, length);
                        count += length;
                    }
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                LogUtil.LogError(ex);
            }

            state.FileBlockMetaData = SerializeHelper.GetModel(metaDataStream) as FileBlockMetaData;
            LogUtil.LogInfo(String.Format("FileName:{0} Guid:{1} Offset:{2} Length:{3}", state.FileBlockMetaData.FileName, state.Guid.ToString(), state.FileBlockMetaData.Offset, state.FileBlockMetaData.Length));
            LogUtil.LogInfo("=====End read MetaData=====");
        }
    }
}